﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using Vibz.SharePoint.Query;
using Vibz.SharePoint.Entity;

namespace Vibz.SharePoint
{
    public class SPWeb
    {
        public delegate T ParseXMLToObject<T>(XElement element);
        SPView _view;
        TeamSite.Lists _teamList;
        public SPUser UserGroup
        {
            get;
            set;
        }
        public SPWeb(object siteUrl)
        {
            _teamList = new TeamSite.Lists();
            _teamList.Url = siteUrl.ToString() + "/_vti_bin/Lists.asmx";
            _teamList.Credentials = System.Net.CredentialCache.DefaultCredentials;
            UserGroup = new SPUser(siteUrl);
            _view = new SPView(siteUrl);
        }
        public void DeleteList(string lstName)
        {
            _teamList.DeleteList(lstName);
        }
        public void DeleteListIfExists(string lstName)
        {
            try
            {
                DeleteList(lstName);
            }
            catch(Exception exc)
            {
            }
        }
        public void CreateList(string lstName, string lstDesc)
        {
            _teamList.AddList(lstName, lstDesc, 100);
        }
        public void CreateList(string lstName, string lstDesc, List<ColumnInfo> columns)
        {
            CreateList(lstName, lstDesc);
            if(columns == null || columns.Count == 0)
                return;
            try
            {
                AddColumns(lstName, columns);
            }
            catch(Exception exc)
            {
                DeleteList(lstName);
                throw new Exception("Error occured while adding column to the list '" + lstName + "'" + exc.Message);
            }
        }
        public void UpdateColumns(string lstName, List<ColumnInfo> columns)
        {
            XmlNode ndList = _teamList.GetList(lstName);
            XmlNode ndVersion = ndList.Attributes["Version"];

            XmlDocument xmlDoc = new System.Xml.XmlDocument();

            XmlNode ndUpdateFields = xmlDoc.CreateNode(XmlNodeType.Element, "Fields", "");

            int i = 1;
            foreach(ColumnInfo column in columns)
            {
                ndUpdateFields.InnerXml += "<Method ID='" + i.ToString() + "'><Field Type='" + column.Type.ToString() +
                    "' DateOnly='TRUE' DisplayName='" + column.ColumnName +
                    "' FromBaseType='TRUE'  Description='" + column.Description + "'/></Method>";
                i++;
            }

            try
            {
                XmlNode ndReturn =
                   _teamList.UpdateList(lstName,
                   null, null, ndUpdateFields, null,
                   ndVersion.Value);

                XmlNamespaceManager nsm = new XmlNamespaceManager(ndReturn.OwnerDocument.NameTable);
                nsm.AddNamespace("sp", ndReturn.NamespaceURI);

                XmlNodeList results = ndReturn.SelectNodes("//sp:Result", nsm);
                foreach(XmlNode result in ndReturn)
                {
                    XmlNode node = result.SelectSingleNode("//sp:ErrorText", nsm);
                    if(node != null)
                        throw new Exception("Sharepoint reported error in request object. " + node.InnerText);
                }
            }
            catch(System.Web.Services.Protocols.SoapException ex)
            {
                throw new Exception("Soap error occured while updating column list '" + lstName + "'. "
                    + ex.Message + "\nDetail:\n" + ex.Detail.InnerText);
            }
            catch(Exception ex)
            {
                throw new Exception("Error occured while updating column list. "
                    + ex.Message);
            }
        }
        public void DeleteColumns(string lstName, string[] columns)
        {
            XmlNode ndList = _teamList.GetList(lstName);
            XmlNode ndVersion = ndList.Attributes["Version"];

            XmlDocument xmlDoc = new System.Xml.XmlDocument();

            XmlNode ndDeleteFields = xmlDoc.CreateNode(XmlNodeType.Element, "Fields", "");

            int i = 1;
            foreach(string column in columns)
            {
                ndDeleteFields.InnerXml += "<Method ID='" + i.ToString() + "'><Field Name='" + column + "'/></Method>";
                i++;
            }

            try
            {
                XmlNode ndReturn =
                   _teamList.UpdateList(lstName,
                   null, null, null, ndDeleteFields,
                   ndVersion.Value);

                XmlNamespaceManager nsm = new XmlNamespaceManager(ndReturn.OwnerDocument.NameTable);
                nsm.AddNamespace("sp", ndReturn.NamespaceURI);

                XmlNodeList results = ndReturn.SelectNodes("//sp:Result", nsm);
                foreach(XmlNode result in ndReturn)
                {
                    XmlNode node = result.SelectSingleNode("//sp:ErrorText", nsm);
                    if(node != null)
                        throw new Exception("Sharepoint reported error in request object. " + node.InnerText);
                }
            }
            catch(System.Web.Services.Protocols.SoapException ex)
            {
                throw new Exception("Soap error occured while deleting column to list '" + lstName + "'. "
                    + ex.Message + "\nDetail:\n" + ex.Detail.InnerText);
            }
            catch(Exception ex)
            {
                throw new Exception("Error occured while deleting column list. "
                    + ex.Message);
            }
        }
        public void AddColumns(string lstName, List<ColumnInfo> columns)
        {
            XmlNode ndList = _teamList.GetList(lstName);
            XmlNode ndVersion = ndList.Attributes["Version"];

            XmlDocument xmlDoc = new System.Xml.XmlDocument();

            XmlNode ndNewFields = xmlDoc.CreateNode(XmlNodeType.Element, "Fields", "");

            int i = 1;
            foreach(ColumnInfo column in columns)
            {
                ndNewFields.InnerXml += "<Method ID='" + i.ToString() + "'>" + "<Field Type='" + column.Type.ToString() +
                    "' DateOnly='TRUE' DisplayName='" + column.ColumnName +
                    "' FromBaseType='TRUE'  Description='" + column.Description + "'"
                    + (column.Type == ColumnType.MultiChoice ? " Format='RadioButtons'" : "")
                    + (column.Type == ColumnType.MultiChoice ? ">" + CreateChoiceString((string[])column.AssociatedItems) + "</Field>" : "/>")
                    + "</Method>";
                i++;
            }


            try
            {
                XmlNode ndReturn =
                   _teamList.UpdateList(lstName,
                   null, ndNewFields, null, null,
                   ndVersion.Value);

                _view.AddColumnToDefaultView(lstName, columns);
                
                XmlNamespaceManager nsm = new XmlNamespaceManager(ndReturn.OwnerDocument.NameTable);
                nsm.AddNamespace("sp", ndReturn.NamespaceURI);

                XmlNodeList results = ndReturn.SelectNodes("//sp:Result", nsm);
                foreach(XmlNode result in ndReturn)
                {
                    XmlNode node = result.SelectSingleNode("//sp:ErrorText", nsm);
                    if(node != null)
                        throw new Exception("Sharepoint reported error in request object. " + node.InnerText);
                }
            }
            catch(System.Web.Services.Protocols.SoapException ex)
            {
                throw new Exception("Soap error occured while adding column to list '" + lstName + "'. "
                    + ex.Message + "\nDetail:\n" + ex.Detail.InnerText);
            }
            catch(Exception ex)
            {
                throw new Exception("Error occured while  adding column to list. "
                    + ex.Message);
            }
        }
        public string CreateChoiceString(string[] columns)
        {
            string retValue = "<CHOICES>";
            foreach(string column in columns)
                retValue += "<CHOICE>" + column + "</CHOICE>";
            retValue += "</CHOICES>";
            return retValue;
        }
        public List<T> GetListRecords<T>(string listName, string[] columns, ParseXMLToObject<T> parseDelegate)
        {
            return GetListRecords<T>(listName, columns, null, parseDelegate);
        }
        public List<T> GetListRecords<T>(string listName, string[] columns, Expression exp, ParseXMLToObject<T> parseDelegate)
        {
            XmlDocument xmlDoc = new System.Xml.XmlDocument();
            XmlNode ndQuery = xmlDoc.CreateNode(XmlNodeType.Element, "Query", "");
            XmlNode ndViewFields = xmlDoc.CreateNode(XmlNodeType.Element, "ViewFields", "");
            XmlNode ndQueryOptions = xmlDoc.CreateNode(XmlNodeType.Element, "QueryOptions", "");

            ndQueryOptions.InnerXml = "<IncludeMandatoryColumns>FALSE</IncludeMandatoryColumns>" 
                + "<DateInUtc>TRUE</DateInUtc>";

            ndQuery.InnerXml = ParseQuery(exp);

            foreach(string column in columns)
                ndViewFields.InnerXml += "<FieldRef Name='" + column + "' />";
            XmlNode ndListItems = null;
            try
            {
                ndListItems = _teamList.GetListItems(listName, null, ndQuery,
                    ndViewFields, null, ndQueryOptions, null);

                var xData = XElement.Parse(ndListItems.InnerXml);
                var results = (from x in xData.DescendantsAndSelf(XName.Get("row", "#RowsetSchema"))
                               select parseDelegate(x));
                if(results != null)
                {
                    return results.ToList();
                }
            }
            catch(System.Web.Services.Protocols.SoapException ex)
            {
                throw new Exception("Soap error occured while querying list '" + listName + "'. " 
                    + ex.Message + "\nDetail:\n" + ex.Detail.InnerText);
            }
            catch(Exception ex)
            {
                throw new Exception("Error occured while querying list. " // + ndListItems.InnerXml 
                    + ex.Message);
            }
            return new List<T>();
        }
        public List<string> GetListColumnChoiceOptions(string list, string column)
        {
            try
            {
                XmlNode node = _teamList.GetList(list);
                XElement el = XElement.Parse(node.OuterXml);
                var choices = from e in el.Elements().Descendants()
                              where
                                e.Name.LocalName == "Field"
                                && e.Attribute("DisplayName") != null
                                && e.Attribute("DisplayName").Value == column
                              from f in e.Descendants()
                              select f.Value;

                return choices.Skip(1).ToList<string>();
            }
            catch(Exception exc)
            {
                return new List<string>();
            }
        }
        string ParseQuery(Expression exp)
        {
            if(exp == null)
                return "";
            else
                return "<Where>" + ParseExpression(exp) + "</Where>";
        }
        string ParseExpression(Expression exp)
        {
            if(exp == null)
                return "";
            if(exp.ExpressionType == ExpressionType.Value)
            {
                return "<" + exp.Operator.ToString() + ">"
                + "<FieldRef Name='" + exp.ColumnName + "'/>"
                + "<Value Type='" + exp.ColumnDataType + "'>" + exp.ColumnValue + "</Value>"
                + "</" + exp.Operator.ToString() + ">";
            }
            else
            {
                return "<" + exp.Operand.ToString() + ">"
                    + ParseExpression(exp.Expression1)
                    + ParseExpression(exp.Expression2)
                    + "</" + exp.Operand.ToString() + ">";
            }
        }

        public int AddListItem(string listName, Dictionary<string, object> columnValueList)
        {
            return AddListItem(listName, columnValueList, "ows_ID");
        }
        public int AddListItem(string listName, Dictionary<string, object> columnValueList, string keyColumn)
        {
            return AddUpdateDeleteListItem("New", listName, columnValueList, keyColumn);
        }
        public int DeleteListItem(string listName, Dictionary<string, object> columnValueList)
        {
            return DeleteListItem(listName, columnValueList, "ID");
        }
        public int DeleteListItem(string listName, Dictionary<string, object> columnValueList, string keyColumn)
        {
            int id = -1;
            if(!columnValueList.Keys.Contains("ID"))
                id = GetItemID(listName, columnValueList);
            
            if(id == -1)
                throw new Exception("No matching records found to be deleted.");

            columnValueList.Add("ID", id);
            return AddUpdateDeleteListItem("Delete", listName, columnValueList, keyColumn);
        }
        public int GetItemID(string listName, Dictionary<string, object> columnValueList)
        {
            XmlDocument xmlDoc = new System.Xml.XmlDocument();
            XmlNode ndQuery = xmlDoc.CreateNode(XmlNodeType.Element, "Query", "");
            XmlNode ndViewFields = xmlDoc.CreateNode(XmlNodeType.Element, "ViewFields", "");
            XmlNode ndQueryOptions = xmlDoc.CreateNode(XmlNodeType.Element, "QueryOptions", "");

            ndQueryOptions.InnerXml = "<IncludeMandatoryColumns>FALSE</IncludeMandatoryColumns>"
                + "<DateInUtc>TRUE</DateInUtc>";

            Expression exp = Expression.Create(columnValueList.ToArray());
            ndQuery.InnerXml = ParseQuery(exp);

            ndViewFields.InnerXml += "<FieldRef Name='ID' />";

            try
            {
                XmlNode ndListItems = _teamList.GetListItems(listName, null, ndQuery,
                    ndViewFields, null, ndQueryOptions, null);

                var xData = XElement.Parse(ndListItems.InnerXml);
                var results = (from x in xData.DescendantsAndSelf(XName.Get("row", "#RowsetSchema"))
                               select int.Parse(x.Attribute("ows_ID").Value));
                if(results != null && results.Count() > 0)
                {
                    return results.ToList()[0];
                }
            }
            catch(System.Web.Services.Protocols.SoapException ex)
            {
                throw new Exception("Soap error occured while querying list '" + listName + "'. "
                    + ex.Message + "\nDetail:\n" + ex.Detail.InnerText);
            }
            catch(Exception ex)
            {
                throw new Exception("Error occured while querying list. "
                    + ex.Message);
            }
            return -1;
        }
        public int UpdateListItem(string listName, Dictionary<string, object> columnValueList)
        {
            return UpdateListItem(listName, columnValueList, "ID");
        }
        public int UpdateListItem(string listName, Dictionary<string, object> columnValueList, string keyColumn)
        {
            if(!columnValueList.Keys.Contains("ID"))
            {
                int id = -1;
                if(columnValueList.ContainsKey(keyColumn))
                {
                    Dictionary<string, object> keyCol = new Dictionary<string, object>();
                    keyCol.Add(keyColumn, columnValueList[keyColumn]);
                    id = GetItemID(listName, keyCol);
                    columnValueList.Add("ID", id);
                }
                else
                    throw new Exception("You must either provide ID value in columnValueList " + 
                        "or send keyColumn argument in columnValueList to update a record.");
            }

            return AddUpdateDeleteListItem("Update", listName, columnValueList, keyColumn);
        }
        public int AddOrUpdateListItem(string listName, Dictionary<string, object> columnValueList)
        {
            return AddOrUpdateListItem(listName, columnValueList, "ID");
        }
        public int AddOrUpdateListItem(string listName, Dictionary<string, object> columnValueList, string keyColumn)
        {
            int id = -1;
            Dictionary<string, object> keyCol = new Dictionary<string, object>();
            if(columnValueList.ContainsKey(keyColumn))
                keyCol.Add(keyColumn, columnValueList[keyColumn]);
            if(!columnValueList.Keys.Contains("ID"))
                id = GetItemID(listName, keyCol);
            string operation = "Update";
            if(id == -1)
                operation = "New";
            else
                columnValueList.Add("ID", id);
            return AddUpdateDeleteListItem(operation, listName, columnValueList, keyColumn);
        }
        int AddUpdateDeleteListItem(string operation, string listName, Dictionary<string, object> columnValueList, string keyColumn)
        {
            XmlNode ndReturn;

            string strBatch = "<Method ID='1' Cmd='" + operation + "'>";
            foreach(string colName in columnValueList.Keys)
                strBatch += "<Field Name='" + colName + "'><![CDATA[" + ParseListObject(columnValueList[colName]) + "]]></Field>";
            strBatch += "</Method>";

            try
            {
                XmlDocument xmlDoc = new System.Xml.XmlDocument();
                XmlElement elBatch = xmlDoc.CreateElement("Batch");

                strBatch = strBatch.Replace("&", "&amp;");
                elBatch.InnerXml = strBatch;

                ndReturn = _teamList.UpdateListItems(listName, (XmlNode)elBatch);

                XmlNamespaceManager nsm = new XmlNamespaceManager(ndReturn.OwnerDocument.NameTable);
                nsm.AddNamespace("sp", ndReturn.NamespaceURI);

                XmlNodeList results = ndReturn.SelectNodes("//sp:Result", nsm);
                foreach(XmlNode result in ndReturn)
                {
                    XmlNode node = result.SelectSingleNode("//sp:ErrorText", nsm);
                    if(node != null)
                        throw new Exception("Sharepoint reported error in request object. " + node.InnerText);
                }

                if(operation == "Delete")
                    return -1;

                if(!keyColumn.StartsWith("ows_"))
                    keyColumn = "ows_" + keyColumn;
                var xData = XElement.Parse(ndReturn.InnerXml);
                try
                {
                    return (from x in xData.DescendantsAndSelf(XName.Get("row", "#RowsetSchema"))
                            select Convert.ToInt32(double.Parse(x.Attribute(keyColumn).Value))).ToArray()[0];
                }
                catch(Exception exc)
                {
                    return -1;
                }
            }
            catch(Exception exc)
            {
                throw new Exception("Could not " + operation + " item with context to list '" + listName + "'." + exc.Message);
            }
        }
        string ParseListObject(object obj)
        {
            if(obj == null)
                return "";
            if(obj.GetType() == typeof(string))
            {
                return obj.ToString();
            }
            else if(obj.GetType() == typeof(int))
            {
                return obj.ToString();
            }
            else if(obj.GetType() == typeof(DateTime))
            {
                return ((DateTime)obj).ToString("yyyy-MM-ddTHH:mm:ssZ");
            }
            return obj.ToString();
        }

        /*
         *  XmlNode ndList = listService.GetList(lstName);
            XmlNode ndVersion = ndList.Attributes["Version"];

            XmlDocument xmlDoc = new System.Xml.XmlDocument();

            XmlNode ndDeleteFields = xmlDoc.CreateNode(XmlNodeType.Element, "Fields", "");
            XmlNode ndProperties = xmlDoc.CreateNode(XmlNodeType.Element, "List", "");
            XmlAttribute ndTitleAttrib = (XmlAttribute)xmlDoc.CreateNode(XmlNodeType.Attribute, "Title", "");
            XmlAttribute ndDescriptionAttrib = (XmlAttribute)xmlDoc.CreateNode(XmlNodeType.Attribute, "Description", "");
            XmlNode ndNewFields = xmlDoc.CreateNode(XmlNodeType.Element, "Fields", "");
            XmlNode ndUpdateFields = xmlDoc.CreateNode(XmlNodeType.Element, "Fields", "");

            //ndTitleAttrib.Value = lstName;
            //ndDescriptionAttrib.Value = "New_Description";

            //ndProperties.Attributes.Append(ndTitleAttrib);
            //ndProperties.Attributes.Append(ndDescriptionAttrib);

            //ndDeleteFields.InnerXml = "<Method ID='5'>" +
            //   "<Field Name='Field1'/></Method>" +
            //   "<Method ID='6'><Field Name='Field2'/>" +
            //   "</Method>";

            ndNewFields.InnerXml = "<Method ID='1'>" +
                "<Field Type='DateTime' DateOnly='TRUE' DisplayName='NewField1' FromBaseType='TRUE'/>" +
                "</Method><Method ID='2'>" + "<Field Type='Text' DisplayName='NewField2' Required='TRUE' FromBaseType='TRUE' Description='Description'/>" +
                "</Method>";

            //ndUpdateFields.InnerXml = "<Method ID='3'>" +
            //    "<Field Type='Number' Name='Field1' DisplayName='Field1_Display' Required='TRUE' FromBaseType='TRUE' Description='Description'/>" + "</Method><Method ID='4'>" +
            //    "<Field Type='Text' Name='Field2' DisplayName='Field2_Display' Required='TRUE' FromBaseType='TRUE' Description='Description'/>" +
            //   "</Method>";

            try
            {
                XmlNode ndReturn =
                   _teamList.UpdateList(lstName,
                   ndProperties, ndNewFields, ndUpdateFields, ndDeleteFields,
                   ndVersion.Value);

                // MessageBox.Show(ndReturn.OuterXml);
            }

            catch(Exception ex)
            {
                // MessageBox.Show("Message:\n" + ex.Message + "\nStackTrace:\n" + ex.StackTrace);
            }
         */
    }
}
